﻿using Microsoft.Practices.ESB.Adapter;
using Microsoft.Practices.ESB.Exception.Management;
using Microsoft.Practices.ESB.Itinerary;
using Microsoft.Practices.ESB.Itinerary.Services;
using Microsoft.Practices.ESB.Resolver;
using Microsoft.Practices.ESB.Transform;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Streaming;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using System.Xml.XPath;
using BizTalk.ESB.Extensions.Validation.Helpers;

namespace BizTalk.ESB.Extensions.MessagingServices
{
    public class MessageValidator : IMessagingService
    {
        private string _BizTalkSystemNamespace = @"http://schemas.microsoft.com/BizTalk/2003/system-properties";
        private string _TracePrefix = @"MessageValidator";

        public IBaseMessage Execute(IPipelineContext context, IBaseMessage msg, string resolverString, IItineraryStep step)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (msg == null)
                throw new ArgumentNullException("msg");
            if (string.IsNullOrEmpty(resolverString))
                throw new ArgumentException("Resolver must be provided", "resolverString");
            if (step == null)
                throw new ArgumentNullException("step");

            try
            {
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Step UUID = " + step.UUId);
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Step ID = " + step.Id);
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Service Instance ID = " + step.ServiceInstanceId);

                ResolverInfo info = ResolverMgr.GetResolverInfo(ResolutionType.Endpoint, resolverString);
                if (!info.Success)
                    throw new RoutingException("Invalid Resolver", resolverString);

                // Resolve configuration for routing.
                Dictionary<string, string> resolverDictionary = ResolverMgr.Resolve(info, msg, context);

                // Set variables from resolver
                var validatorParams = new BreValidationInstructions();

                validatorParams.ValidationDocType = resolverDictionary["Resolver.DocumentType"];
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Resolver.DocumentType = " + validatorParams.ValidationDocType);
                validatorParams.ValidationPolicyName = resolverDictionary["Resolver.PolicyName"];
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Resolver.PolicyName = " + validatorParams.ValidationPolicyName);
                validatorParams.ValidationBreTrace = resolverDictionary["Resolver.BreTracePath"];
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Resolver.BreTracePath = " + validatorParams.ValidationBreTrace);
                validatorParams.ValidationEnabled = System.Convert.ToBoolean(resolverDictionary["Resolver.Enabled"]);
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Resolver.Enabled = " + validatorParams.ValidationEnabled.ToString());
                validatorParams.StopOnError = System.Convert.ToBoolean(resolverDictionary["Resolver.StopOnError"]);
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Resolver.StopOnError = " + validatorParams.StopOnError.ToString());
                validatorParams.RoutingIsRequestResponse = System.Convert.ToBoolean(resolverDictionary["Resolver.IsRequestResponse"]);
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Resolver.IsRequestResponse = " + validatorParams.RoutingIsRequestResponse.ToString());
                validatorParams.RoutingServiceAction = resolverDictionary["Resolver.ServiceAction"];
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Resolver.ServiceAction = " + validatorParams.RoutingServiceAction);
                validatorParams.RoutingServiceName = resolverDictionary["Resolver.ServiceName"];
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Resolver.ServiceName = " + validatorParams.RoutingServiceName);
                validatorParams.RoutingServiceType = resolverDictionary["Resolver.ServiceType"];
                System.Diagnostics.Trace.WriteLine(_TracePrefix + " - Resolver.ServiceType = " + validatorParams.RoutingServiceType);


                if (msg.BodyPart != null && (!string.IsNullOrEmpty(validatorParams.ValidationPolicyName))
                    && (!string.IsNullOrEmpty(validatorParams.ValidationDocType)) && validatorParams.ValidationEnabled)
                {
                    var originalStrm = msg.BodyPart.GetOriginalDataStream();

                    if (originalStrm != null)
                    {
                        // Validate Message
                        try
                        {
                            ValidateMessage(msg, context, step, validatorParams);
                        }
                        catch (SystemException exValidation)
                        {
                            if (validatorParams.StopOnError)
                            {
                                throw exValidation;
                            }
                        }
                    }
                }

                return msg;
            }
            catch (System.Exception ex)
            {
                EventLogger.Write(MethodInfo.GetCurrentMethod(), ex);
                throw;
            }    
        }

        public string Name
        {
            get { return "BizTalk.ESB.Extensions.MessagingServices.MessageValidator"; }
        }

        public bool ShouldAdvanceStep(IItineraryStep step, IBaseMessage msg)
        {
            return true;
        }

        public bool SupportsDisassemble
        {
            get { return false; }
        }

        #region Private Methods

        private void ValidateMessage(IBaseMessage message, IPipelineContext pContext, IItineraryStep step, BreValidationInstructions validationParams)
        {
            if (String.IsNullOrEmpty(validationParams.ValidationDocType))
            {
                throw new ArgumentException("Validation XML Document Type cannot be null", "validationParams.ValidationDocType");
            }
            else if (String.IsNullOrEmpty(validationParams.ValidationPolicyName))
            {
                throw new ArgumentException("Validation BRE Policy Name cannot be null", "validationParams.ValidationPolicyName");
            }
            else
            {
                var xmlMsg = new XmlDocument();
                xmlMsg.LoadXml(this.GetMsgContent(pContext, message));

                bool ValidationHasErrors;
                var msgValidationResults = BizTalk.ESB.Extensions.Validation.Helpers.Validator.ValidateWithBRE(xmlMsg
                    , validationParams.ValidationPolicyName, validationParams.ProcessName, validationParams.ValidationDocType
                    , validationParams.DebugFlag, out ValidationHasErrors, validationParams.ValidationBreTrace);

                if (string.IsNullOrEmpty(validationParams.RoutingIsRequestResponse.ToString()) == false &&
                    string.IsNullOrEmpty(validationParams.RoutingServiceAction) == false &&
                    string.IsNullOrEmpty(validationParams.RoutingServiceName) == false &&
                    string.IsNullOrEmpty(validationParams.RoutingServiceType) == false)
                {
                    // Publish Message ?
                }

                if (validationParams.HasValidationErrors && validationParams.StopOnError)
                {
                    throw new ServiceException("Message Validation Failed. Raw validation content: " + msgValidationResults.OuterXml);
                }
            }
        }

        private String GetMessageContent(IPipelineContext pContext, IBaseMessage message)
        {
            //var messageSB = new StringBuilder(); //Save message in this StringBuilder
            var copyStream = new VirtualStream(); //Copy the original stream to the a new stream 
            var copyStreamWriter = new StreamWriter(copyStream); //Use a stream writer to copy original stream to copyStream
            ReadOnlySeekableStream seekableStream = new ReadOnlySeekableStream(message.BodyPart.GetOriginalDataStream());

            String msgData = string.Empty;
            //Stream outStream = new VirtualStream();
            //StreamWriter strmWriter = new StreamWriter(outStream);

            using (StreamReader sr = new
                          StreamReader(message.BodyPart.GetOriginalDataStream()))
            {
                msgData = sr.ReadToEnd();
                //strmWriter.Flush(); // flush writer to ensure writing's done.
                //strmWriter.Close(); // close stream since it won't be used
                sr.Close();
            }

            //reset stream
            copyStreamWriter.Flush(); //Ensure everything has been written to the new stream
            copyStream.Seek(0, SeekOrigin.Begin); //Set the new stream to the beginning
            message.BodyPart.Data = copyStream; //Set the Stream of the message leaving it's integrity intact

            pContext.ResourceTracker.AddResource(copyStream); //Ensure these resources are tracked and ultimately disposed
            pContext.ResourceTracker.AddResource(copyStreamWriter); //Ensure these resources are tracked and ultimately disposed

            return msgData;
        }

        private string GetMsgContent(IPipelineContext pipeLineContext, IBaseMessage baseMessage)
        {
            //extract message
            var message = new StringBuilder(); //Save message in this StringBuilder
            var copyStream = new VirtualStream(); //Copy the original stream to the a new stream 
            var copyStreamWriter = new StreamWriter(copyStream); //Use a stream writer to copy original stream to copyStream
            ReadOnlySeekableStream seekableStream = new ReadOnlySeekableStream(baseMessage.BodyPart.GetOriginalDataStream());

            //Read OriginalStream
            using (StreamReader streamReader = new StreamReader(seekableStream))
            {

                var line = string.Empty; //Read Stream Line by Line
                while ((line = streamReader.ReadLine()) != null)
                {
                    message.AppendLine(line); //Save Message to StringBuilder
                    copyStreamWriter.WriteLine(line); //Copy to new stream
                }

                copyStreamWriter.Flush(); //Ensure everything has been written to the new stream
                copyStream.Seek(0, SeekOrigin.Begin); //Set the new stream to the beginning
                baseMessage.BodyPart.Data = copyStream; //Set the Stream of the message leaving it's integrity intact

                pipeLineContext.ResourceTracker.AddResource(copyStream); //Ensure these resources are tracked and ultimately disposed
                pipeLineContext.ResourceTracker.AddResource(copyStreamWriter); //Ensure these resources are tracked and ultimately disposed

            }
            return message.ToString();
        }

        #endregion
    }
}
